# -*- coding: utf-8 -*-
"""
Brief  : 书法机器人笔画编辑器
Author : 杨应科
Date   : 2020.7.23
"""

from Tkinter import *
from numpy import *
from PIL import Image,ImageTk
from shutil import copyfile
import tkMessageBox
import tkFileDialog
import copy
import os
import json

root = None  # 正经的root用的是self.initWindow,本全局变量仅用于防止重复开启

class StrokeCreator(object):
    # 绘图相关参数
    imgSave = None
    img = None
    imgPath = 'misc/default.png'
    canvas = Canvas
    canvasMenu = Menu
    canvasZ = Canvas
    canvasZMenu = Menu
    points = []  # [[x,y,z],...]
    zCord = []  # [[x,z],...]
    pointsPos = StringVar
    pointsPosLabel = Label
    pointNum = StringVar
    pointNumLabel = Label
    cursorState = StringVar
    cursorStateLabel = Label
    Mode = StringVar
    ModeLabel = Label
    imgSizeXEntr = Entry
    strokeNameEntr = Entry
    imgCanvasID = 0
    canvasWidth = 500.0   
    canvasHeight = 500.0
    canvasZWidth = 400.0
    canvasZHeight = 200.0
    imgSize = [0, 0]
    btn1x = 600
    btn1y = 40
    pointIndex = 0
    draggingFlag = False
    draggingIndex = 0
    zDraggingFlag = False
    zDraggingIndex = 0
    pointSize = 8.0

    def __init__(self,initWindow):
        global root
        if root != None: 
            initWindow.destroy()
            tkMessageBox.showinfo("已启动","请勿重复开启笔画生成器!")
            return
        self.initWindow = initWindow
        root = initWindow
        self.initWindow.title("笔画编辑器")
        self.initWindow.geometry('1024x700')
        self.initWindow.resizable(width = False, height = False)
        self.img_offset_pix = [0, 0]
        self.import_stroke_type = '3aixs'
        self.GUI_Init()
    
    def GUI_Init(self):
        #----------------画布-------------------
        # 读取图片
        imgOrg = Image.open(self.imgPath)
        imgResized = imgOrg.resize((int(self.canvasWidth), int(self.canvasHeight)), Image.ANTIALIAS)
        self.img = ImageTk.PhotoImage(imgResized)
        # 创建画布
        self.canvas = Canvas(self.initWindow, bg = "gray", width = self.canvasWidth, height = self.canvasHeight)
        self.imgCanvasID = self.canvas.create_image(int(self.canvasWidth / 2), int(self.canvasHeight / 2), image = self.img)
        self.canvas.place(x = 45, y = 30)
        self.canvasZ = Canvas(self.initWindow, bg = "white", width = self.canvasZWidth, height = self.canvasZHeight)
        self.canvasZ.place(x = 600, y = 470)
        self.canvasZ.create_line(0, self.canvasZHeight / 2, self.canvasZWidth, self.canvasZHeight / 2, width = 1.2)
        # 画布菜单
        self.canvasMenu = Menu(self.canvas, tearoff = 0)
        self.canvasMenu.add_command(label = 'AddPoint', command = self.AddPointBtnCb)
        self.canvasMenu.add_command(label = 'DeletePoint', command = self.DelPointBtnCb)
        self.canvasMenu.add_command(label = 'ReturnToNormal', command = self.Rtn2NormalBtnCb)
        self.canvasMenu.add_separator()
        self.canvasMenu.add_command(label = 'ClearAll', command = self.ClearAllBtnCb)
        # 绑定鼠标事件
        self.canvas.bind('<Button-1>', self.OnCanvasLeftButtonDown)
        self.canvas.bind('<B1-Motion>', self.OnCanvasLeftButtonMove)
        self.canvas.bind('<ButtonRelease-1>', self.OnCanvasLeftButtonUp)
        self.canvas.bind('<ButtonRelease-3>', self.OnCanvasRightButtonUp)
        self.canvasZ.bind('<Button-1>', self.OnCanvasZLeftButtonDown)
        self.canvasZ.bind('<B1-Motion>', self.OnCanvasZLeftButtonMove)
        self.canvasZ.bind('<ButtonRelease-1>', self.OnCanvasZLeftButtonUp)
        # 动态标签
        self.cursorState = StringVar(self.initWindow, "Normal")
        self.cursorStateLabel = Label(self.initWindow, textvariable = self.cursorState, font = 'SimHei -15', fg = 'green') 
        self.cursorStateLabel.place(x = self.canvasWidth - 10, y = 5)
        self.pointsPos = StringVar(self.initWindow, "请输入笔画真实尺寸")
        self.pointsPosLabel = Label(self.initWindow, textvariable = self.pointsPos, font = 'Simhei -15', fg = 'red')
        self.pointsPosLabel.place(x = 45, y = 5)
        self.pointNum = StringVar(self.initWindow, "当前笔画点的个数为: 0")
        self.pointNumLabel = Label(self.initWindow, textvariable = self.pointNum, font = 'Simhei -15', fg = 'black')
        self.pointNumLabel.place(x = 45, y = 550)
        self.Mode = StringVar(self.initWindow, "Normal")
        self.ModeLabel = Label(self.initWindow, textvariable = self.Mode, font = 'SimHei -15', fg = 'green')
        self.ModeLabel.place(x = 950, y = 445)
        # 静态标签
        imgSizeXLabel = Label(self.initWindow, text = "长边尺寸(mm):", font = 'SimHei -15', fg = 'black')
        imgSizeXLabel.place(x = self.btn1x + 180, y = self.btn1y + 200)
        commentLabel = Label(self.initWindow, text = "注：请输入长边尺寸", font = 'SimHei -15', fg = 'black')
        commentLabel.place(x = self.btn1x + 180, y = self.btn1y + 240)
        strokeNameLabel = Label(self.initWindow, text = "笔画名称:", font = 'SimHei -15', fg = 'black')
        strokeNameLabel.place(x = self.btn1x + 180, y = self.btn1y + 120)
        canvasZLabel = Label(self.initWindow, text = "拖动控制点调节落笔深度", font = 'Simhei -15', fg = 'black')
        canvasZLabel.place(x = 600, y = 445)
        canvasZaxisLabel1 = Label(self.initWindow, text = "0", font = 'Simhei -15', fg = 'black')
        canvasZaxisLabel1.place(x = 570, y = 460)
        canvasZaxisLabel2 = Label(self.initWindow, text = "0.25", font = 'Simhei -15', fg = 'black')
        canvasZaxisLabel2.place(x = 570, y = 510)
        canvasZaxisLabel3 = Label(self.initWindow, text = "0.5", font = 'Simhei -15', fg = 'black')
        canvasZaxisLabel3.place(x = 570, y = 560)
        canvasZaxisLabel4 = Label(self.initWindow, text = "0.75", font = 'Simhei -15', fg = 'black')
        canvasZaxisLabel4.place(x = 562, y = 610)
        canvasZaxisLabel5 = Label(self.initWindow, text = "1", font = 'Simhei -15', fg = 'black')
        canvasZaxisLabel5.place(x = 570, y = 660)
        #----------------按钮-------------------
        setimgBtn = Button(self.initWindow, width = '20', height = '4', text = "设置真实尺寸", font = 'MingLiU -13', command = self.SetimgBtnCb)
        setimgBtn.place(x = self.btn1x, y = self.btn1y + 100)
        exportBtn = Button(self.initWindow, width = '20', height = '4', text = "导出笔画", font = 'MingLiU -13', command = self.ExportBtnCb)
        exportBtn.place(x = self.btn1x, y = self.btn1y + 200)
        importPicBtn = Button(self.initWindow, width = '20', height = '4', text = "导入笔画图片", font = 'MingLiU -13', command = self.ImportPicCb)
        importPicBtn.place(x = self.btn1x, y = self.btn1y)
        importStrokeBtn = Button(self.initWindow, width = '20', height = '4', text = "导入笔画", font = 'MingLiU -13', command = self.ImportStrokeCb)
        importStrokeBtn.place(x = self.btn1x, y = self.btn1y + 300)
        #----------------输入框-----------------
        self.imgSizeXEntr = Entry(self.initWindow, width = '10')
        self.imgSizeXEntr.place(x = self.btn1x + 300, y = self.btn1y + 200)
        self.strokeNameEntr = Entry(self.initWindow, width = '20')
        self.strokeNameEntr.place(x = self.btn1x + 260, y = self.btn1y + 120) 
        # ---------------------------------- 启动 ----------------------------------------
        self.initWindow.protocol("WM_DELETE_WINDOW", self.OnClose)  # 注册关闭事件

    def OnClose(self):
        global root
        root = None
        self.initWindow.destroy()

    def AddPointBtnCb(self):
        """进入增加位置点模式"""
        self.cursorState.set("AddPoint")
        self.canvas["cursor"] = "cross"
        self.cursorStateLabel['fg'] = 'blue'
    
    def DelPointBtnCb(self):
        """进入删除位置点模式"""
        self.cursorState.set("DelPoint")
        self.canvas["cursor"] = "X_cursor"
        self.cursorStateLabel['fg'] = 'red'
    
    def Rtn2NormalBtnCb(self):
        """回到一般模式"""
        self.cursorState.set("Normal")
        self.canvas["cursor"] = ""
        self.cursorStateLabel['fg'] = 'green'
    
    def ClearAllBtnCb(self):
        """清除所有点"""
        self.Rtn2NormalBtnCb()
        self.points = []
        self.canvas.delete("all")
        self.imgCanvasID = self.canvas.create_image(self.canvasWidth / 2, self.canvasHeight / 2, image = self.img)
        self.pointIndex = 0
        self.pointNum.set("当前笔画点的个数为: {}".format(self.pointIndex))
        self.canvasZ.delete("all")
        self.canvasZ.create_line(0, self.canvasZHeight / 2, self.canvasZWidth, self.canvasZHeight / 2, width = 1.2)
        self.zCord = []
    
    def OnCanvasLeftButtonDown(self,event):
        state = self.cursorState.get()
        self.canvasMenu.unpost()
        if state == "Normal":  # 若在点上，进入拖动状态
            for index, point in enumerate(self.points):
                if event.x > point[0] - self.pointSize / 2 and event.x < point[0] + self.pointSize / 2 and \
                   event.y > point[1] - self.pointSize / 2 and event.y < point[1] + self.pointSize / 2:
                    self.draggingFlag = True
                    self.draggingIndex = index
        elif state == "AddPoint":
            pass
        elif state == "DelPoint":
            pass

    def OnCanvasLeftButtonMove(self, event):
        """按下左键移动，若图片尺寸已确定，则显示x，y在图中的实际坐标
           若有点处于被拖动状态，则不断更新其坐标
        """
        if self.imgSize[0] != 0 and self.imgSize[1] != 0:
            curX = (event.x - self.img_offset_pix[0]) * self.imgSize[0] / self.canvasWidth 
            curY = (event.y - self.img_offset_pix[1]) * self.imgSize[1] / self.canvasHeight
            if curX > self.imgSize[0] or curY > self.imgSize[1] or curX < 0 or curY < 0: return
            self.pointsPosLabel['fg'] = 'green'
            self.pointsPos.set("X:{:.2f} Y:{:.2f}".format(curX, curY))
        if self.draggingFlag == True:
            self.points[self.draggingIndex][0] = event.x  # 更新控制点
            self.points[self.draggingIndex][1] = event.y
            self.UpdateCanvas()
    
    def OnCanvasLeftButtonUp(self, event):
        state = self.cursorState.get()
        if state == "Normal":
            self.draggingFlag = False  # 离开拖动状态
        elif state == "AddPoint":  #按一定逻辑添加点
            for point in self.points:  #若该点已存在，则不重复添加
                if event.x > point[0] - self.pointSize / 2 and event.x < point[0] + self.pointSize / 2 and \
                   event.y > point[1] - self.pointSize / 2 and event.y < point[1] + self.pointSize / 2:
                   return
            self.DrawPoint(event.x, event.y)
            self.points.append([event.x, event.y, 0]) #  存的是画布上的像素坐标
            self.pointIndex += 1
            self.pointNum.set("当前笔画点的个数为: {}".format(self.pointIndex))
            if self.pointIndex == 0:
                self.canvasZ.delete("all")
                self.canvasZ.create_line(0, self.canvasZHeight / 2, self.canvasZWidth, self.canvasZHeight / 2, width = 1.2)
            else:
                gap = self.canvasZWidth / (self.pointIndex + 1)
                self.zCord = []
                for index, point in enumerate(range(self.pointIndex), start = 1):
                    self.zCord.append([index * gap, self.canvasZHeight / 2])
                self.UpdateCanvasZ()
        elif state == "DelPoint":  #遍历队列point,按一定逻辑删除点
            for point in self.points:
                if event.x > point[0] - self.pointSize / 2 and event.x < point[0] + self.pointSize / 2 and \
                   event.y > point[1] - self.pointSize / 2 and event.y < point[1] + self.pointSize / 2:
                   self.points.remove(point)
                   self.pointIndex -= 1
                   self.pointNum.set("当前笔画点的个数为: {}".format(self.pointIndex))
                   self.UpdateCanvas() #更新画布
            if self.pointIndex == 0:
                self.canvasZ.delete("all")
                self.canvasZ.create_line(0, self.canvasZHeight / 2, self.canvasZWidth, self.canvasZHeight / 2, width = 1.2)
            else:
                gap = self.canvasZWidth / (self.pointIndex + 1)
                self.zCord = []
                for index, point in enumerate(range(self.pointIndex), start = 1):
                    self.zCord.append([index * gap, self.canvasZHeight / 2])
                self.UpdateCanvasZ()
                
    
    def OnCanvasRightButtonUp(self, event):
        self.canvasMenu.post(event.x_root, event.y_root)
    

    def OnCanvasZLeftButtonDown(self, event):
        if self.import_stroke_type == '6aixs': tkMessageBox.showinfo("提示","导入的笔画是由动捕记录导出的6轴笔画，不支持Z轴的调整。", parent=self.initWindow);return
        self.Mode.set("Dragging")
        self.ModeLabel['fg'] = 'blue'
        self.zDraggingFlag = True
    
    def OnCanvasZLeftButtonMove(self, event):
        self.zDraggingIndex = len(self.zCord) + 1
        if  self.zDraggingFlag == True:
            curX = event.x
            curY = event.y
            for index, point in enumerate(self.zCord):
                if  curX > point[0] - self.pointSize / 2 and curX < point[0] + self.pointSize / 2:
                    self.zDraggingIndex = index
                    self.zCord[self.zDraggingIndex][1] = curY
                    self.UpdateCanvasZ()
                    for index, point in enumerate(self.points):
                        point[2] = self.zCord[index][1] / self.canvasZHeight
                    break
        else:
            pass

    def OnCanvasZLeftButtonUp(self, event):
        self.Mode.set("Normal")
        self.ModeLabel['fg'] = 'green'
        self.zDraggingFlag = False
    
    def SetimgBtnCb(self):
        """设定真实尺寸"""
        if self.imgPath == 'misc/default.png': tkMessageBox.showinfo("提示","请选择图片", parent=self.initWindow);return
        if self.imgSizeXEntr.get() == "": tkMessageBox.showinfo("提示","请输入真实尺寸", parent=self.initWindow);return
        self.imgSize = [float(self.imgSizeXEntr.get()), float(self.imgSizeXEntr.get())]
        self.pointsPosLabel['fg'] = 'green'
        self.pointsPos.set("按住鼠标左键可查看坐标")
    
    def ImportStrokeCb(self):
        """导入笔画,若导入带六轴信息笔画的则不予处理Z轴"""
        importPath = tkFileDialog.askopenfilename(title = '导入笔画文件', filetypes = [('笔画', '*.json')], parent=self.initWindow)
        if importPath == "" or importPath == (): return
        with open(importPath, 'r') as f:
            jsonImp = json.load(f)
        self.imgPath = 'calligraphy_lib/strokes/' + jsonImp["preview_img"]
        if os.path.isfile(self.imgPath):
            imgOrg = Image.open(self.imgPath)
            self.imgSave = imgOrg
            width = imgOrg.width
            height = imgOrg.height
            if height > width: #  Y轴较长
                resizeHeight = self.canvasHeight
                scale = self.canvasHeight / height
                resizeWidth = width * scale
                self.img_offset_pix = [int((self.canvasWidth-resizeWidth)/2), 0]
            else: #  X轴较长
                resizeWidth = self.canvasWidth
                scale = self.canvasWidth / width
                resizeHeight = height * scale
                self.img_offset_pix = [0, int((self.canvasHeight-resizeHeight)/2)]
            imgResized = imgOrg.resize((int(resizeWidth), int(resizeHeight)), Image.ANTIALIAS)
            self.img = ImageTk.PhotoImage(imgResized)
            self.canvas.delete(self.imgCanvasID)
            self.imgCanvasID = self.canvas.create_image(self.canvasWidth / 2, self.canvasHeight / 2, image = self.img)
        else:
            tkMessageBox.showinfo("提示","笔画预览图不存在", parent=self.initWindow);return
        self.strokeNameEntr.delete(0, END)
        self.strokeNameEntr.insert(0, jsonImp["stroke_name"])
        realSize = jsonImp["stroke_size"]
        if realSize[0] > realSize[1]: realSize[1] = realSize[0]
        else: realSize[0] = realSize[1]
        self.imgSizeXEntr.delete(0, END)
        self.imgSizeXEntr.insert(0, str(realSize[0]))
        self.SetimgBtnCb()
        self.pointIndex = len(jsonImp["stroke_points"])
        self.pointNum.set("当前笔画点的个数为: {}".format(self.pointIndex))
        if(len(jsonImp["stroke_points"][0])==3): self.import_stroke_type = '3aixs'
        elif(len(jsonImp["stroke_points"][0])==6): self.import_stroke_type = '6aixs'
        impPoints = jsonImp["stroke_points"]
        imageOrgX = (self.canvasWidth - resizeWidth) / 2
        imageOrgY = (self.canvasHeight - resizeHeight) / 2
        for point in impPoints:
            point[0] = point[0] * self.canvasWidth / self.imgSize[0]
            point[0] += imageOrgX
            point[1] = point[1] * self.canvasHeight / self.imgSize[1]
            point[1] += imageOrgY
        self.points = impPoints
        self.UpdateCanvas()
        if self.import_stroke_type == '3aixs':  # 若导入的为6轴笔画，不更新Z画布
            gap = self.canvasZWidth / (self.pointIndex + 1)
            self.zCord = []
            for index in enumerate(range(self.pointIndex), start = 1):
                self.zCord.append([index[0] * gap, self.canvasZHeight * self.points[index[0] - 1][2]])
            self.UpdateCanvasZ()
        elif self.import_stroke_type == '6aixs':
            tkMessageBox.showinfo("提示","导入的笔画是由动捕记录导出的6轴笔画，不支持Z轴的调整。", parent=self.initWindow);return

    def ImportPicCb(self):
        """导入笔画图片"""
        importPath = tkFileDialog.askopenfilename(title = '导入笔画图片', filetypes = [('笔画图片', '*.png')], parent=self.initWindow)
        # 读入并缩放场地图片
        if importPath == "" or importPath == (): return
        self.imgPath = importPath
        imgOrg = Image.open(self.imgPath)
        self.imgSave = imgOrg
        width = imgOrg.width
        height = imgOrg.height
        if height > width: #  Y轴较长
            resizeHeight = self.canvasHeight
            scale = self.canvasHeight / height
            resizeWidth = width * scale
            self.img_offset_pix = [int(self.canvasWidth-resizeWidth), 0]
        else: #  X轴较长
            resizeWidth = self.canvasWidth
            scale = self.canvasWidth / width
            resizeHeight = height * scale
            self.img_offset_pix = [0, int(self.canvasHeight-resizeHeight)]
        imgResized = imgOrg.resize((int(resizeWidth), int(resizeHeight)), Image.ANTIALIAS)
        self.img = ImageTk.PhotoImage(imgResized)
        self.canvas.delete(self.imgCanvasID)
        self.imgCanvasID = self.canvas.create_image(self.canvasWidth / 2, self.canvasHeight / 2, image = self.img)
        self.ClearAllBtnCb()
    
    def ExportBtnCb(self):
        if self.imgPath == 'misc/default.png': tkMessageBox.showinfo("提示","请选择图片", parent=self.initWindow);return
        if self.imgSize == [0,0]: self.SetimgBtnCb()
        expPoints = copy.deepcopy(self.points)
        imgOrg = Image.open(self.imgPath)
        width = imgOrg.width
        height = imgOrg.height
        if height > width: #  Y轴较长
            resizeHeight = self.canvasHeight
            scale = self.canvasHeight / height
            resizeWidth = width * scale
            realHeight = self.imgSize[1]
            scale = height / realHeight
            realWidth = width / scale
        else: #  X轴较长
            resizeWidth = self.canvasWidth
            scale = self.canvasWidth / width
            resizeHeight = height * scale
            realWidth = self.imgSize[0]
            scale = width / realWidth
            realHeight = height / scale
        imageOrgX = (self.canvasWidth - resizeWidth) / 2
        imageOrgY = (self.canvasHeight - resizeHeight) / 2
        for point in expPoints:
            point[0] -= imageOrgX
            point[0] = point[0] / self.canvasWidth * self.imgSize[0]
            point[1] -= imageOrgY
            point[1] = point[1] / self.canvasHeight * self.imgSize[1]
        if not self.strokeNameEntr.get():
            tkMessageBox.showinfo("提示","请输入笔画名称", parent=self.initWindow)
            return
        strokeName = self.strokeNameEntr.get()
        jsonExp = json.dumps({'stroke_name': strokeName, 'stroke_size': [realWidth, realHeight], 
        'stroke_points': expPoints, 'preview_img': strokeName + '.png'}, indent = 4, ensure_ascii = False)
        if not os.path.exists('calligraphy_lib/strokes'): os.makedirs('calligraphy_lib/strokes')
        with open('calligraphy_lib/strokes/' + strokeName + '.json', 'w') as f:
            f.write(jsonExp)
        self.imgSave.save('calligraphy_lib/strokes/' + strokeName + '.png')
        tkMessageBox.showinfo('提示', '笔画保存成功', parent=self.initWindow)
    
    def DrawPoint(self, x, y):
        return self.canvas.create_oval(x - self.pointSize / 2, y - self.pointSize / 2, \
                                        x + self.pointSize / 2, y + self.pointSize / 2, fill = "#FF0000")
    
    def DrawZPoint(self, x, y):
        return self.canvasZ.create_oval(x - self.pointSize / 2, y - self.pointSize / 2, \
                                        x + self.pointSize / 2, y + self.pointSize / 2, fill = "#FF0000", outline = 'white')
    
    def UpdateCanvasZ(self):
        self.canvasZ.delete("all")
        self.canvasZ.create_line(0, self.canvasZHeight / 2, self.canvasZWidth, self.canvasZHeight / 2, width = 1.2)
        for point in self.zCord:
            self.DrawZPoint(point[0] , point[1])

    def UpdateCanvas(self):
        """清空画布，并根据点重新绘制"""
        self.canvas.delete("all")
        self.imgCanvasID = self.canvas.create_image(self.canvasWidth / 2, self.canvasHeight / 2, image = self.img)
        for point in self.points:
            self.DrawPoint(point[0], point[1])


if __name__ == "__main__":
    foo = Tk()
    app = StrokeCreator(foo)
    foo.mainloop()
